package com.dapeng.cleardatabackend.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dapeng.cleardatabackend.annotation.MultiTransactional;
import com.dapeng.cleardatabackend.common.ErrorCode;
import com.dapeng.cleardatabackend.common.LoginUserHolder;
import com.dapeng.cleardatabackend.exception.BusinessException;
import com.dapeng.cleardatabackend.exception.ThrowUtils;
import com.dapeng.cleardatabackend.mapper.NodeMapper;
import com.dapeng.cleardatabackend.model.dto.node.NodeAddRequest;
import com.dapeng.cleardatabackend.model.dto.node.NodeDeleteRequest;
import com.dapeng.cleardatabackend.model.dto.node.NodeGetRequest;
import com.dapeng.cleardatabackend.model.dto.node.NodeUpdateRequest;
import com.dapeng.cleardatabackend.model.entity.Graph;
import com.dapeng.cleardatabackend.model.entity.Node;
import com.dapeng.cleardatabackend.model.entity.Relation;
import com.dapeng.cleardatabackend.model.entity.User;
import com.dapeng.cleardatabackend.model.vo.NodeVO;
import com.dapeng.cleardatabackend.service.GraphService;
import com.dapeng.cleardatabackend.service.NodeService;
import com.dapeng.cleardatabackend.service.RelationService;
import com.dapeng.cleardatabackend.utils.CsvUtils;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author dapeng
 * @description 针对表【node(节点表)】的数据库操作Service实现
 * @createDate 2024-12-22 13:29:10
 */
@Service
public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node>
        implements NodeService {

    @Resource
    private Neo4jClient neo4jClient;

    @Resource
    @Lazy
    private GraphService graphService;

    @Resource
    private RelationService relationService;

    @Override
    public void saveNodeToNeo4j(List<Node> nodeList, Long graphId) {
        List<Map<String, String>> nodes = nodeList.stream().map(node -> {
            HashMap<String, String> map = new HashMap<>();
            map.put("nodeId", node.getNodeId());
            map.put("label", node.getLabel());
            map.put("text", node.getText());
            return map;
        }).collect(Collectors.toList());
        String query = String.format(
                "CALL apoc.periodic.iterate(" +
                        "'UNWIND $nodes AS node RETURN node', " +
                        "'MERGE (n:`%s` { nodeId: node.nodeId, label: node.label, text: node.text})', " +
                        "{batchSize: 5000, parallel: true, concurrency: 8, params: {nodes: $nodes}})",
                graphId
        );
        neo4jClient
                .query(query)
                .bind(nodes).to("nodes")
                .run();
    }

    @Override
    public void hardRemoveByGraphId(Long graphId) {
        baseMapper.hardDeleteByGraphId(graphId);
    }

    @Override
    public Page<NodeVO> getNodePage(NodeGetRequest nodeGetRequest) {
        Long graphId = nodeGetRequest.getGraphId();
        String nodeId = nodeGetRequest.getNodeId();
        String label = nodeGetRequest.getLabel();
        String text = nodeGetRequest.getText();
        int current = nodeGetRequest.getCurrent();
        int pageSize = nodeGetRequest.getPageSize();
        Graph graph = graphService.getById(graphId);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        Page<Node> page = new Page<>(current, pageSize);
        LambdaQueryWrapper<Node> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Node::getGraphId, graphId)
                .eq(!StrUtil.isBlankIfStr(nodeId), Node::getNodeId, nodeId)
                .like(!StrUtil.isBlankIfStr(label), Node::getLabel, label)
                .like(!StrUtil.isBlankIfStr(text), Node::getText, text);
        Page<Node> nodePage = this.page(page, queryWrapper);
        Page<NodeVO> nodeVOPage = new Page<>(nodePage.getCurrent(), nodePage.getSize(), nodePage.getTotal());
        nodeVOPage.setRecords(nodePage.getRecords().stream().map(NodeVO::beanToVo).collect(Collectors.toList()));
        return nodeVOPage;
    }

    @Override
    @MultiTransactional
    public Long addNode(NodeAddRequest nodeAddRequest) {
        Long graphId = nodeAddRequest.getGraphId();
        String nodeId = nodeAddRequest.getNodeId();
        String label = nodeAddRequest.getLabel();
        String text = nodeAddRequest.getText();
        User loginUser = LoginUserHolder.getLoginUser();
        Graph graph = graphService.getById(graphId);
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        Node node = new Node();
        node.setNodeId(nodeId);
        node.setGraphId(graphId);
        node.setLabel(label);
        node.setText(text);
        LambdaQueryWrapper<Node> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Node::getGraphId, graphId).eq(Node::getNodeId, nodeId);
        boolean existed = baseMapper.exists(queryWrapper);
        ThrowUtils.throwIf(existed, ErrorCode.OPERATION_ERROR, "节点已存在");
        boolean save = this.save(node);
        ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "添加节点失败");
        HashMap<String, Object> params = new HashMap<>();
        params.put("nodeId", nodeId);
        params.put("label", label);
        params.put("text", text);
        String query = String.format(
                "MERGE (n:`%s` {nodeId: $nodeId, label: $label, text: $text}) ",
                graphId
        );
        neo4jClient
                .query(query)
                .bindAll(params)
                .run();
        return node.getId();
    }

    @Override
    @MultiTransactional
    public Boolean updateNode(NodeUpdateRequest nodeUpdateRequest) {
        Long graphId = nodeUpdateRequest.getGraphId();
        Long id = nodeUpdateRequest.getId();
        String label = nodeUpdateRequest.getLabel();
        String text = nodeUpdateRequest.getText();
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(StrUtil.isBlankIfStr(label) && StrUtil.isBlankIfStr(text), ErrorCode.PARAMS_ERROR, "至少填写一项");
        Graph graph = graphService.getById(graphId);
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        Node node = getById(id);
        String oldNodeId = node.getNodeId();
        Node newNode = new Node();
        newNode.setId(id);
        newNode.setLabel(label);
        newNode.setText(text);
        StringBuilder queryBuilder = new StringBuilder();
        queryBuilder.append(String.format("MATCH (n:`%s` {nodeId: $oldNodeId}) SET ", graphId));
        if (!StrUtil.isBlankIfStr(label)) {
            queryBuilder.append("n.label = $label");
        }
        if (!StrUtil.isBlankIfStr(text)) {
            if (!StrUtil.isBlankIfStr(label)) {
                queryBuilder.append(", ");
            }
            queryBuilder.append("n.text = $text");
        }
        queryBuilder.append(" RETURN 'success' AS status");
        String query = queryBuilder.toString();
        HashMap<String, Object> params = new HashMap<>();
        params.put("oldNodeId", oldNodeId);
        params.put("label", label);
        params.put("text", text);
        Optional<Map<String, Object>> result = neo4jClient
                .query(query)
                .bindAll(params)
                .fetch()
                .one();
        ThrowUtils.throwIf(!result.isPresent(), ErrorCode.OPERATION_ERROR, "更新节点失败");
        boolean update = this.updateById(newNode);
        ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "更新节点失败");
        return true;
    }

    @Override
    @MultiTransactional
    public Boolean deleteNode(NodeDeleteRequest nodeDeleteRequest) {
        Long id = nodeDeleteRequest.getId();
        Long graphId = nodeDeleteRequest.getGraphId();
        User loginUser = LoginUserHolder.getLoginUser();
        Graph graph = graphService.getById(graphId);
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        Node node = getById(id);
        ThrowUtils.throwIf(node == null || !Objects.equals(node.getGraphId(), graphId), ErrorCode.OPERATION_ERROR, "节点不存在");
        boolean remove = this.removeById(id);
        ThrowUtils.throwIf(!remove, ErrorCode.OPERATION_ERROR, "删除节点失败");
        String nodeId = node.getNodeId();
        LambdaQueryWrapper<Relation> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Relation::getGraphId, graphId)
                .and(wrapper -> wrapper.eq(Relation::getStartNodeId, nodeId)
                        .or().eq(Relation::getEndNodeId, nodeId));
        relationService.remove(queryWrapper);
        HashMap<String, Object> params = new HashMap<>();
        params.put("nodeId", nodeId);
        String query = String.format(
                "MATCH (n:`%s` {nodeId: $nodeId}) " +
                        "WITH n " +
                        "DETACH DELETE n " +
                        "RETURN 'success' AS result",
                graphId
        );
        Optional<Map<String, Object>> result = neo4jClient
                .query(query)
                .bindAll(params)
                .fetch()
                .one();
        ThrowUtils.throwIf(!result.isPresent(), ErrorCode.OPERATION_ERROR, "删除节点失败");
        return true;
    }

    @Override
    @MultiTransactional
    public Boolean deleteNodeByGraphId(Long graphId) {
        remove(new LambdaQueryWrapper<Node>().eq(Node::getGraphId, graphId));
        String queryDeleteNodes = String.format("MATCH (n:`%s`) DETACH DELETE n", graphId);
        neo4jClient.query(queryDeleteNodes).run();
        return true;
    }

    @Override
    public ResponseEntity<byte[]> downloadOriginalCsv(Long id) {
        // 获取图谱和用户信息
        Graph graph = graphService.getById(id);
        User loginUser = LoginUserHolder.getLoginUser();
        ThrowUtils.throwIf(graph == null || !loginUser.getId().equals(graph.getUserId()), ErrorCode.PARAMS_ERROR, "图谱不存在");
        List<Node> nodeList = baseMapper.selectList(new LambdaQueryWrapper<Node>().eq(Node::getGraphId, id));
        ThrowUtils.throwIf(CollectionUtils.isEmpty(nodeList), ErrorCode.OPERATION_ERROR, "图谱节点为空");

        // 设置响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
        String filename = String.format("%s节点文件.csv", graph.getName());
        try {
            String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
            headers.setContentDispositionFormData("attachment", encodedFilename);
        } catch (UnsupportedEncodingException e) {
            headers.setContentDispositionFormData("attachment", filename);
        }

        StringWriter stringWriter = new StringWriter();
        CSVFormat csvFormat = CsvUtils.getCsvFormat(",");
        try (CSVPrinter csvPrinter = new CSVPrinter(stringWriter, csvFormat)) {
            for (Node node : nodeList) {
                csvPrinter.printRecord(node.getNodeId(), node.getLabel(), node.getText());
            }
        } catch (IOException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成文件失败");
        }

        byte[] fileContent = stringWriter.toString().getBytes(StandardCharsets.UTF_8);
        return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
    }

}



